﻿//------------------------------------------------------------------------------------------------- 
// <copyright file="CompilerUtility.cs" company="Christopher Hines">
// Copyright (c) Christopher Hines. All rights reserved.
// </copyright>
// <summary>
//    Responsible for runtime compilation of dynamic methods
// </summary>
//-------------------------------------------------------------------------------------------------

namespace Copperfield
{
    using System;
    using System.Collections.Generic;
    using System.Diagnostics.CodeAnalysis;
    using System.Linq;
    using System.Linq.Expressions;

    /// <summary>
    /// Responsible for runtime compilation of dynamic methods
    /// </summary>
    internal static class CompilerUtility
    {
        /// <summary>
        /// Compiles and returns a delegate for generated contexts compatible with a given type
        /// </summary>
        /// <param name="contextType">The type of context relevant to the type</param>
        /// <returns>The compiled comptaible contexts delegate</returns>
        public static Func<Type, IEnumerable<IComparable>> CompileRelatedContextsDelegate(Type contextType)
        {
            if (!typeof(IComparable).IsAssignableFrom(contextType))
            {
                throw new ArgumentException("The context type must be a type of IBusinessContext", "contextType");
            }

            var instanceHelper = new DynamicMethodHelper();

            var method = instanceHelper.GetType().GetMethod("BuildRelatedContextsForType", new[] { typeof(Type) });

            if (method == null)
            {
                return null;
            }

            var genericMethod = method.MakeGenericMethod(new[] { contextType });

            var typeRef = Expression.Parameter(typeof(Type), "type");
            var callRef = Expression.Call(Expression.Constant(instanceHelper), genericMethod, new Expression[] { typeRef });
            var lambda = Expression.Lambda<Func<Type, IEnumerable<IComparable>>>(callRef, new[] { typeRef });

            return lambda.Compile();
        }

        /// <summary>
        /// Returns a delegate that will retrieve multiple instances of a plugin type
        /// </summary>
        /// <param name="pluginType">
        /// The plugin Type.
        /// </param>
        /// <returns>
        /// The multiple instances delegate
        /// </returns>
        public static Func<IEnumerable<object>, object> CompileConvertToFlatObjectDelegate(Type pluginType)
        {
            var helper = new DynamicMethodHelper();
            var method = helper.GetType().GetMethod("BuildCollectionFlattenerForType", new[] { typeof(IEnumerable<object>) });

            if (method == null)
            {
                return null;
            }

            var genericMethod = method.MakeGenericMethod(pluginType);

            var typeRef = Expression.Parameter(typeof(IEnumerable<object>), "instances");
            var callRef = Expression.Call(Expression.Constant(helper), genericMethod, new Expression[] { typeRef });
            var lambda = Expression.Lambda<Func<IEnumerable<object>, object>>(callRef, typeRef);

            return lambda.Compile();
        }

        /// <summary>
        /// Helper class responsible for assisting with dynamic method creation
        /// </summary>
        internal class DynamicMethodHelper
        {
            /// <summary>
            /// Returns a list of context values compatible with a specified type
            /// </summary>
            /// <typeparam name="T">The type of context</typeparam>
            /// <param name="type">The type for which compatible contexts should be generated</param>
            /// <returns>The collection of compatible contexts</returns>
            [SuppressMessage("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode", Justification = "Compiler Warning Bug Workaround")]
            [SuppressMessage("Microsoft.Performance", "CA1822:MarkMembersAsStatic", Justification = "Reflection Helper Class")]
            public IEnumerable<T> BuildRelatedContextsForType<T>(Type type) where T : IComparable, new()
            {
                // Try to get the compatible contexts for the type from the cache
                var contexts = ContextConfigurationFactory.GetInstance<IEnumerable<T>>(type.FullName);

                // If the collection was in the cache, return it
                if (contexts != null)
                {
                    return contexts;
                }

                // Initialize Variables
                var filterAttributes = TypeUtility.GetContextFilterAttributes(type);

                // Get all public properties from the type
                var properties = TypeUtility.GetCachedProperties(typeof(T));

                // Convert the properties to a Dictionary<
                var propertyDictionary = properties.ToDictionary(
                    property => property.Name,
                    property =>
                        {
                            var attribute = filterAttributes.FirstOrDefault(p => p.AppliesTo(property.PropertyType));
                            return attribute != null
                                       ? attribute.Values
                                       : new List<object>();
                        });

                // The collection of context permutations in obejct form
                var listOfKeyValuePairCollections = new List<List<KeyValuePair<string, object>>>();
                foreach (var kvp in propertyDictionary)
                {
                    var tempList = new List<List<KeyValuePair<string, object>>>();

                    if (listOfKeyValuePairCollections.Count == 0)
                    {
                        foreach (var value in kvp.Value)
                        {
                            var list = new List<KeyValuePair<string, object>>();
                            list.Add(new KeyValuePair<string, object>(kvp.Key, value));
                            tempList.Add(list);
                        }
                    }
                    else
                    {
                        foreach (var value in kvp.Value)
                        {
                            foreach (var list in listOfKeyValuePairCollections)
                            {
                                var kvpList = new List<KeyValuePair<string, object>>();
                                kvpList.AddRange(list);
                                kvpList.Add(new KeyValuePair<string, object>(kvp.Key, value));
                                tempList.Add(kvpList);
                            }
                        }
                    }

                    listOfKeyValuePairCollections = tempList;
                }

                // Generate the Context Objects from the Permutations Collection
                var contextList = new List<T>();
                foreach (var list in listOfKeyValuePairCollections)
                {
                    var context = new T();

                    foreach (var kvp in list)
                    {
                        var propName = kvp.Key;
                        var props = properties.Where(p => p.Name == propName);

                        foreach (var prop in props)
                        {
                            prop.SetValue(context, kvp.Value, null);
                        }
                    }

                    contextList.Add(context);
                }

                // Store and Return the Result
                ContextConfigurationFactory.For<IEnumerable<T>>().Named(type.FullName).Use(contextList);

                return contextList;
            }

            /// <summary>
            /// Returns a delegate to return an object from a set of instances
            /// </summary>
            /// <param name="instances">
            /// The instances.
            /// </param>
            /// <typeparam name="T">
            /// The type of object
            /// </typeparam>
            /// <returns>
            /// The collection delegate
            /// </returns>
            [SuppressMessage("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode", Justification = "Compiler Warning Bug Workaround")]
            [SuppressMessage("Microsoft.Performance", "CA1822:MarkMembersAsStatic", Justification = "Reflection Helper Class")]
            public object BuildCollectionFlattenerForType<T>(IEnumerable<object> instances)
            {
                var contextualCollection = new ContextualCollection<T>();
                contextualCollection.AddRange(instances.OfType<T>());
                return contextualCollection;
            }
        }
    }
}
